﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Timers;
using System.Windows.Threading;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading;

namespace SwarmMappingAlpha
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        //The roborealm controller
        RRController rrController;
        //List<Node> hardCodedList;


        //Communication controller
        nxtComm.NXTComm robotCommunication;

        //Data Structure Controller
        World world;

        //Last Selected Edge - used to reset last highlighted edge
        Line oldLine;
        System.Windows.Media.Brush oldLineColor;

        //The gui's collection of active drones
        List<Drones> guisCopyOfDrones;

        public static int worldHeight;
        int index = 0;


        /// <summary>
        /// Creates the main application window
        /// Subscribes to the delegates from the controllers
        /// </summary>
        public MainWindow()
        {
            rrController = new RRController();
            guisCopyOfDrones = new List<Drones>();
            robotCommunication = new nxtComm.NXTComm();
            InitializeComponent();

            //hardCodedList = new List<Node>();
            //hardCodedList.Add(new Node(80,80,1));
            //hardCodedList.Add(new Node(200,200,1));
            //hardCodedList.Add(new Node(200,80,1));
            //hardCodedList.Add(new Node(80,200,1));

            index = 0;
            //Sets the drone listView to use the list Of Drones as it's item Source
            droneListView.ItemsSource = guisCopyOfDrones;

            // Subscribe to our roborealm controller to recieve updates when our drone collection changes
            rrController.dronesCollectionModified += new RRController.dronesState(rrController_dronesCollectionModified);

            //subscribe to our communication controller to recieve updates when messages are received
            robotCommunication.messageReceived += new nxtComm.NXTComm.message(robotCommunication_messageReceived);
            robotCommunication.rxtx += new nxtComm.NXTComm.updateStatusBox(robotCommunication_rxtx);
        }

        void robotCommunication_rxtx(string message)
        {
            UpdateStatusWindow(message);
        }   

        /// <summary>
        /// Connect to RoboRealm
        /// Uses our roborealm controller to open the connection to roborealm.
        /// Reads in the dimensions RoboRealm is operating under and sets the canvas's to use the same dimensions
        /// (The canvases are nested in a viewBox so sizing should be a problem)
        /// Enables the user to click createNodes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ConnectToRRClick(object sender, RoutedEventArgs e)
        {
            try
            {
                rrController.connectToRR();
                NodeCanvas.Width = RRController.dimension.width;
                NodeCanvas.Height = RRController.dimension.height;
                EdgeCanvas.Width = RRController.dimension.width;
                EdgeCanvas.Height = RRController.dimension.height;
                RobotCanvas.Width = RRController.dimension.width;
                RobotCanvas.Height = RRController.dimension.height;
                createNodesButton.IsEnabled = true;
                worldHeight = RRController.dimension.height;
            }
            catch (Exception excpt)
            {
                updateStatusLabel(excpt.Message);
            }
        }

        /// <summary>
        /// Populate the nodes based on the information entered in the gui and the dimensions we got from robo realm
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void createNodesClick(object sender, RoutedEventArgs e)
        {
            //Get the node size from the UI
            int result;
            int.TryParse(nodeSizeTextBox.Text, out result);
            if (result > 0)
            {
                //Create a new DS Controller with the RR dimensions and the node size
                world = new World(RRController.dimension.height - result, RRController.dimension.width - result, result);

                //If we dont have RR use hardcoded dimensions for testing
                //world = new World(400, 400, result);
                setNodeDisplay(world.getNodeCollection());

                //Set the edge list view to use the edge collection inside our data structure controller
                edgeListView.ItemsSource = world.edgeCollection;
            }
        }

        /// <summary>
        /// Populates the nodeCanvas with nodes, called once the node dictionairy is generated 
        /// </summary>
        /// <param name="Nodes">a list of nodes to use to populate the node Canvas</param>
        private void setNodeDisplay(Dictionary<String, Node> Nodes)
        {
            NodeCanvas.Children.Clear();
            foreach (KeyValuePair<String, Node> x in Nodes)
            {
                NodeCanvas.Children.Add(x.Value.nodeAsRectangle);
            }
        }


        /// <summary>
        /// Manually adds an edge to the data sturcture and to the UI\
        /// Pulls data from the ui, parses it, then adds it to the data structure controller
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void addEdgeClick(object sender, RoutedEventArgs e)
        {
            if (world != null)
            {

                //Parse the data in the ui and add it to the data structure controller's list of edges
                int result = int.MaxValue;
                int.TryParse(addEdgeWidthTextBox.Text, out result);
                world.addNewEdge(addEdgeN1TextBox.Text, addEdgeN2TextBox.Text, result);

                //Update the listView after we've modified its source collection
                edgeListView.Items.Refresh();

                //Update the edgeCanvas
                repaintEdgeCanvas();
            }
        }

        /// <summary>
        /// Highlight the selected edge
        /// Modifies the z index of the selected edge
        /// Sets the old edge back to its old color
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HighlightSelectedEdge(object sender, SelectionChangedEventArgs e)
        {
            //Set the old edge back to it's old color and old Z index
            if (oldLine != null)
            {
                oldLine.Stroke = oldLineColor;
                Canvas.SetZIndex(oldLine, 0);
            }

            //Set the selected edge based on the selected item in the ListView
            if (((ListView)e.Source).SelectedIndex != -1)
            {
                Line myLine = (Line)EdgeCanvas.Children[edgeListView.SelectedIndex];
                oldLineColor = myLine.Stroke;
                myLine.Stroke = System.Windows.Media.Brushes.Red;
                Canvas.SetZIndex(myLine, 100);
                oldLine = myLine;
            }
        }

        /// <summary>
        /// Repaint the edgeCanvas
        /// Clears the canvas and loops through the edges and reAdds them
        /// </summary>
        public void repaintEdgeCanvas()
        {
            EdgeCanvas.Children.Clear();
            foreach (Edge edg in edgeListView.Items)
            {
                EdgeCanvas.Children.Add(edg.connectingLine);
            }
        }

        /// <summary>
        /// Event Handler fired when Robo Realm Controller recieves updated drone data
        /// </summary>
        /// <param name="dronesList">A list containing updated drones</param>
        private void rrController_dronesCollectionModified(List<Drones> dronesList)
        {
            //Clear our current drone data
            guisCopyOfDrones.Clear();

            //Update our drone data with the new info
            foreach (Drones drone in dronesList)
                guisCopyOfDrones.Add(drone);

            //Update the drone canvas with the new information
            this.Dispatcher.Invoke(DispatcherPriority.Normal, new System.Windows.Forms.MethodInvoker(delegate()
            {
                RobotCanvas.Children.Clear();
                Drones[] copyOfDronesArray = new Drones[guisCopyOfDrones.Count];
                guisCopyOfDrones.CopyTo(copyOfDronesArray);
                foreach (Drones drone in copyOfDronesArray)
                {
                    RobotCanvas.Children.Add(drone.robotAsRectangle);
                }
                droneListView.Items.Refresh();
            }));
        }

        /// <summary>
        /// Event fired from Communication Controller
        /// We've recieved a message from a drone
        /// </summary>
        /// <param name="recievedMessage">The payload we received from the drone</param>
        /// <param name="instructionType">The instruction type</param>
        private void robotCommunication_messageReceived(int[] recievedMessage, int instructionType)
        {
            switch (instructionType)
            {
                //The drone wants to know if it has made it to it's destination
                case 3:
                    {
                        //Calculate the distance between the target and the actual location
                        int DronesDistanceFromTarget = world.calcualteDistance(guisCopyOfDrones[0].cog_x, guisCopyOfDrones[0].cog_y,recievedMessage[0],recievedMessage[1]);

                        //Check to see if the drone is within an acceptable distance from its target before asking for the edge
                        if (DronesDistanceFromTarget < 20)
                        {
                            UpdateStatusWindow("Drone Tasking: Upload Edge");
                            robotCommunication.writeCommunication();
                            break;
                        }

                        int theta = world.calculateHeading(guisCopyOfDrones[0].cog_x, guisCopyOfDrones[0].cog_y, recievedMessage[0], recievedMessage[1]);
                        UpdateStatusWindow("Theta: " + theta);
                        robotCommunication.writeCommunication(theta, DronesDistanceFromTarget);
                        UpdateStatusWindow("Drone Tasking: NTY");
                        break;
                    }

                //The drone sent us an edge
                case 4:
                    {
                        if (world != null)
                        {
                            UpdateStatusWindow("Drone Data: Uploading Edge & sending new instructions");
                            //Add the edge to the world
                            this.Dispatcher.Invoke(DispatcherPriority.Normal, new System.Windows.Forms.MethodInvoker(delegate()
                            {
                                    world.addNewEdge(recievedMessage[0], recievedMessage[1], recievedMessage[2], recievedMessage[3], recievedMessage[4]);
                                    edgeListView.Items.Refresh();
                                    repaintEdgeCanvas();
                            }));


                            //Tell the drone to goto the next edge
                            Node nextNode = world.getNextEdgeToVisit();
                            //Node nextNode = hardCodedList[index];
                            index++;
                            

                            int newDroneHeading = world.calculateHeading(guisCopyOfDrones[0].cog_x, guisCopyOfDrones[0].cog_y, nextNode.X, nextNode.Y);

                            int newDroneDistance = world.calcualteDistance(guisCopyOfDrones[0].cog_x, guisCopyOfDrones[0].cog_y, nextNode.X, nextNode.Y);

                            robotCommunication.writeCommunication(newDroneHeading, newDroneDistance, recievedMessage[2], recievedMessage[3], nextNode.X, nextNode.Y);
                        }

                        break;
                    }

                //The drone sent us the wrong instruction type 
                default:
                    break;
            }
        }

        /// <summary>
        /// Loads an external File containing random edges
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void importRandomEdges(object sender, RoutedEventArgs e)
        {
            if (world != null)
            {
                //Loads edges.txt in the working directory
                StreamReader reader = File.OpenText("edges.txt");

                //Loop through and pull in the edges
                while (reader.Peek() != -1)
                {
                    string line = reader.ReadLine();
                    string first = line.Split(',')[0];
                    string second = line.Split(',')[1];
                    int third = int.Parse(line.Split(',')[2]);
                    world.addNewEdge(first, second, third);
                }

                //We've got them all in now draw them and update the listView
                edgeListView.Items.Refresh();
                repaintEdgeCanvas();
            }
        }

        /// <summary>
        /// Adds the passed message to the staus bar in the UI
        /// </summary>
        /// <param name="message"></param>
        private void updateStatusLabel(String message)
        {
            this.Dispatcher.Invoke(DispatcherPriority.Normal, new System.Windows.Forms.MethodInvoker(delegate()
            {
                statusLabel.Content = message;
            })); 
        }

        /// <summary>
        /// Adds the passed message to the staus window in the UI
        /// </summary>
        /// <param name="message"></param>
        private void UpdateStatusWindow(String message)
        {
            this.Dispatcher.Invoke(DispatcherPriority.Normal, new System.Windows.Forms.MethodInvoker(delegate()
            {
                statusWindow.Text = message + "\n" + statusWindow.Text;
            }));
            
        }



        private void startMappingButtonClick(object sender, RoutedEventArgs e)
        {

            Node nextNode = world.getNextEdgeToVisit();

            if (nextNode != null && guisCopyOfDrones != null)
            {
                Drones[] copyOfDronesArray = new Drones[guisCopyOfDrones.Count];
                guisCopyOfDrones.CopyTo(copyOfDronesArray);

                if (copyOfDronesArray[0] != null)
                {
                    int newDroneHeading = world.calculateHeading(copyOfDronesArray[0].cog_x, copyOfDronesArray[0].cog_y, 80, 80);
                    UpdateStatusWindow("Theta: " + newDroneHeading);
                    int newDroneDistance = world.calcualteDistance(copyOfDronesArray[0].cog_x, copyOfDronesArray[0].cog_y, 80, 80);
                    robotCommunication.writeCommunication(newDroneHeading, newDroneDistance, 40,40,80,80);
                }
            }
        }

        private void sendHardCodedData(object sender, RoutedEventArgs e)
        {
            int hardcodedHeading;
            int.TryParse(HeadingTextbox.Text, out hardcodedHeading);

            int hardcodedDistance;
            int.TryParse(DistanceTextBox.Text, out hardcodedDistance);

            //addEdgeN1TextBox.Text, addEdgeN2TextBox.Text

            Node node1 = world.getNodeCollection()[StartNodeTextBox.Text];
            Node node2 = world.getNodeCollection()[EndNodeTextBox.Text];
            //robotCommunication_rxtx("theta: " + world.calculateHeading(guisCopyOfDrones[0].cog_x, guisCopyOfDrones[0].cog_y, node2.X, node2.Y) + " distance: " + world.calcualteDistance(guisCopyOfDrones[0].cog_x, guisCopyOfDrones[0].cog_y, node2.X, node2.Y));
            robotCommunication_rxtx("theta: " + world.calculateHeading(node1.X, node1.Y, node2.X, node2.Y) + " distance: " + world.calcualteDistance(node1.Y, node1.X, node2.Y, node2.X));
            robotCommunication.writeCommunication(world.calculateHeading(node1.X, node1.Y, node2.X, node2.Y),
                                                  world.calcualteDistance(node1.Y, node1.X, node2.Y, node2.X)
                                                  , node1.Y, node1.X, node2.Y, node2.X);
        }

        private void generateMSTClick(object sender, RoutedEventArgs e)
        {
           
            world.MST();
            List<Edge> MST = new List<Edge>();
            MST = world.getMinimumSpanningTree();
            MstCanvas.Children.Clear();
            try
            {
                foreach (Edge edg in MST)
                {
                    Line myLine = new Line();
                    myLine.X1 = edg.connectingLine.X1;
                    myLine.X2 = edg.connectingLine.X2;
                    myLine.Y1 = edg.connectingLine.Y1;
                    myLine.Y2 = edg.connectingLine.Y2;
                    myLine.StrokeThickness = 2;
                    myLine.Stroke = Brushes.Black;

                    //myLine.Fill = Brushes.Black;
                    MstCanvas.Children.Add(myLine);
                }
            }
            catch (Exception ex)
            {
               
            }
        }
    }


    /// <summary>
    /// Converter for xaml binding
    /// Allows checkboxs to bind to ui elements visibility properties
    /// </summary>
    public class BooleanToHiddenVisibility : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            Visibility rv = Visibility.Visible;
            try
            {
                var x = bool.Parse(value.ToString());
                if (x)
                {
                    rv = Visibility.Visible;
                }
                else
                {
                    rv = Visibility.Collapsed;
                }
            }
            catch (Exception)
            {
            }
            return rv;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value;
        }

    }
}
